/**
 * @file    image_recognition.c
 * @brief   测试讯飞图片识别api
 * @author  zyh <2972140442@qq.com>
 * @date    2025-06-29
 * @license GPL-2.0
 * @note    使用前请先注册讯飞的物体识别服务，将APPID和APIKEY
 *          修改为自己的，使用终端操作，切换到build目录，执行
 *          "cmake .."，然后执行"make"，最后运行生成的程序
 *          （build目录下的main），执行"./main <图片路径>",
 *          结果会在终端输出
 */
 #include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "Base64.h"
#include "md5.h"
#include "cJSON.h"
#include "csv_parse.h"

// 调试用错误输出宏
#define LOG_ERROR(fmt, ...) \
    fprintf(stderr, "[ERROR] %s:%d (%s): " fmt "\n", \
            __FILE__, __LINE__, __func__, ##__VA_ARGS__)

#define DOMAIN  "tupapi.xfyun.cn"                     // 服务器域名
#define PORT    80                                 // 服务器端口
                               
#define APPID "00244fd0"
#define APIKEY "8affd9a7e83a2b62cf0ce34049e5b36d"
#define REV_BUF 4096 // 接收缓冲区大小
#define BODY_BUF 4096


int connect_server()
{
    // 根据域名获取IP地址
    struct hostent *host;
    host = gethostbyname(DOMAIN);
    if ( NULL == host )
    {
        printf("gethostbyname failed,error: %d\n", h_errno);
        exit(EXIT_FAILURE);        
    }
    //创建套接字
    int sockfd;
    if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) 
    { 
        LOG_ERROR("socket failed,error: %d, %s\n", errno, strerror(errno));
        exit(errno);
    }

    // 创建并初始化服务器信息结构体
    struct sockaddr_in servaddr;
    bzero(&servaddr, sizeof(servaddr)); //每个字节都用0填充
    servaddr.sin_family = AF_INET; //使用IPv4地址
    servaddr.sin_port = htons(PORT); //端口
    servaddr.sin_addr = *((struct in_addr *)host->h_addr);

    // 创建连接
    if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
    {
        LOG_ERROR("connect failed,error: %d, %s\n", errno, strerror(errno));
        close(sockfd);
        exit(errno);
    }
    printf("connect success!\n\n");

    return sockfd;
}

int create_request_header(const char *image_name,char *rev_buf, size_t content_length)
{
    // 创建HTTP请求头部
    time_t current_time = time(NULL);

    // 构造X-Param原始json串
    cJSON *param_json = cJSON_CreateObject();
    if ( NULL == param_json )
    {
        cJSON_Delete(param_json); 
        return -1;
    }
    if (cJSON_AddStringToObject(param_json, "image_name", image_name) == NULL)
    {
        cJSON_Delete(param_json);
        return -1;
    }
    if (cJSON_AddStringToObject(param_json, "image_url", "") == NULL)
    {
        cJSON_Delete(param_json);
        return -1;
    }
    char *pr = cJSON_Print(param_json);
    if (pr == NULL)
    {
        printf("Failed to print param_json.\n");
        cJSON_Delete(param_json);
        return -1;
    }
    printf("param json格式创建成功：\n");
    printf("%s\n",pr);

    // 将param json字符串转成base64格式
    char *x_param = base64_encode(pr, strlen(pr), NULL);
    printf("param base64 字符串：%s\n",x_param);

    // 生成MD5令牌
    char CheckSum[512];
    char checksum_md5[33];
    memset(CheckSum, 0, sizeof(CheckSum));
    sprintf(CheckSum, "%s%ld%s", APIKEY, current_time, x_param);
    md5_encode(CheckSum, checksum_md5, 0);
    printf("令牌：%s\n",CheckSum);
    printf("md5令牌：%s\n",checksum_md5);

    // 合并HTTP请求头
    memset(rev_buf,0,sizeof(rev_buf));
    sprintf(rev_buf,    "POST http://tupapi.xfyun.cn/v1/currency HTTP/1.1\r\n"
                        "Host: %s\r\n"
                        "Content-Type: application/octet-stream\r\n"
                        "Content-Length: %zu\r\n"
                        "X-Appid: %s\r\n"
                        "X-CurTime: %ld\r\n"
                        "X-Param: %s\r\n"
                        "X-CheckSum: %s\r\n\r\n", DOMAIN, content_length, APPID, current_time, x_param, checksum_md5); 
                
    printf("请求头：\n%s\n",rev_buf);

    // 释放资源
    cJSON_Delete(param_json);
    free(x_param);

    return 0;
}

int main(int argc, char **argv)
{
    // 判断输入
    if (argc != 2) 
    {
        printf("用法: %s <图片路径>\n", argv[0]);
        return 1;
    }
    const char *image_path = argv[1];

    // 读取图片文件
    FILE *image_file = fopen(image_path, "rb");
    if (image_file == NULL) 
    {
        LOG_ERROR("无法打开图片文件: %s, error: %d, %s\n", 
                  image_path, errno, strerror(errno));
        exit(errno);
    }

    // 获取图片大小
    fseek(image_file, 0, SEEK_END);
    size_t image_size = ftell(image_file);
    rewind(image_file);

    // 分配内存存储图片数据
    unsigned char *image_data = malloc(image_size);
    if (image_data == NULL) 
    {
        LOG_ERROR("内存分配失败\n");
        fclose(image_file);
        return ENOMEM;
    }

    // 读取图片数据
    size_t bytes_read = fread(image_data, 1, image_size, image_file);
    if (bytes_read != image_size) 
    {
        LOG_ERROR("读取图片失败, 读取了 %zu 字节, 期望 %zu 字节\n", 
                  bytes_read, image_size);
        free(image_data);
        fclose(image_file);
        return EIO;
    }
    fclose(image_file);

    // 提取图片文件名
    const char *image_name = strrchr(image_path, '/');
    if (image_name == NULL) 
    {
        image_name = image_path;
    } 
    else 
    {
        image_name++;  // 跳过斜杠
    }

    // 创建HTTP请求头
    char request_header[1024];
    create_request_header(image_name,request_header,image_size);

    // 连接服务器
    int sockfd = connect_server();
    // 发送HTTP请求头
    ssize_t header_bytes_sent = send(sockfd, request_header, strlen(request_header), 0);
    if (header_bytes_sent < 0) 
    {
        LOG_ERROR("发送请求头失败, error: %d, %s\n", errno, strerror(errno));
        free(image_data);
        close(sockfd);
        return errno;
    } 
    else if ((size_t)header_bytes_sent != strlen(request_header)) 
    {
        LOG_ERROR("发送请求头不完整, 发送了 %zd 字节, 期望 %zu 字节\n", 
                  header_bytes_sent, strlen(request_header));
        free(image_data);
        close(sockfd);
        return EIO;
    }
    printf("请求头发送成功, 长度: %zd 字节\n", header_bytes_sent);

    // 发送图片数据（即请求体）
    ssize_t total_bytes_sent = 0;
    while (total_bytes_sent < (ssize_t)image_size) 
    {
        ssize_t bytes_sent = send(sockfd, image_data + total_bytes_sent, image_size - total_bytes_sent, 0);
        if (bytes_sent < 0) 
        {
            LOG_ERROR("发送图片数据失败, error: %d, %s\n", errno, strerror(errno));
            break;
        }
        total_bytes_sent += bytes_sent;
    }   
    if (total_bytes_sent == (ssize_t)image_size) 
    {
        printf("图片数据发送成功, 长度: %zd 字节\n", total_bytes_sent);
    } 
    else 
    {
        LOG_ERROR("发送图片数据不完整, 发送了 %zd 字节, 期望 %zu 字节\n", 
                  total_bytes_sent, image_size);
        free(image_data);
        close(sockfd);
        return EIO;
    }

    // 释放图片数据内存
    free(image_data);

    // 接收HTTP响应
    printf("等待服务器响应...\n");
    char response_buffer[REV_BUF];
    char body_bufer[BODY_BUF] = {0};
    ssize_t bytes_received;
    int content_length = -1;
    int in_header = 1;
    size_t total_response_size = 0;

    // 接收并解析响应头
    while ((bytes_received = recv(sockfd, response_buffer + total_response_size, REV_BUF - total_response_size - 1, 0)) > 0) 
    {
        total_response_size += bytes_received;
        response_buffer[total_response_size] = '\0';

        // 查找响应头结束标记
        char *header_end = strstr(response_buffer, "\r\n\r\n");
        if (in_header && header_end) 
        {
            // 解析Content-Length
            char *content_length_line = strstr(response_buffer, "Content-Length:");
            if (content_length_line) 
            {
                content_length_line += strlen("Content-Length:");
                while (*content_length_line == ' ' || *content_length_line == '\t') 
                {
                    content_length_line++;
                }
                content_length = atoi(content_length_line);
                printf("Content-Length: %d\n", content_length);
            }
            
            // 打印响应头
            size_t header_length = header_end - response_buffer + 2;  // 包含\r\n\r\n
            printf("响应头:\n%.*s\n", (int)header_length, response_buffer);
            
            // 处理响应体
            char *body = header_end + 4;  // 跳过\r\n\r\n
            size_t body_length = total_response_size - (body - response_buffer);
            if (body_length > 0) 
            {
                // 将响应体数据写入缓冲区
                sprintf(body_bufer, "%s", body);
            }
            
            in_header = 0;
        } 
        else if (!in_header) 
        {
            // 将响应体数据写入缓冲区
            sprintf(body_bufer, "%s", response_buffer);
        }
        
        // 检查是否接收完所有数据
        if (content_length >= 0 && total_response_size >= (size_t)content_length) 
        {
            break;
        }
    }

    if (bytes_received < 0) 
    {
        LOG_ERROR("接收响应失败, error: %d, %s\n", errno, strerror(errno));
    } 
    else if (bytes_received == 0) 
    {
        printf("服务器关闭连接\n");
    }


    // 对相应体数据进行解析
    int label = -1;
    cJSON *body_json = cJSON_Parse(body_bufer);
    if (body_json == NULL)
    {
        const char *error_ptr = cJSON_GetErrorPtr();
        if (error_ptr != NULL)
        {
            fprintf(stderr, "Error before: %s\n", error_ptr);
        }
    }
    // 输出完整相应体
    char *body_str = cJSON_Print(body_json);    
    printf("%s\n",body_str);
    // 获取响应体关键数据，对数据进行处理
    const cJSON *rev_code = cJSON_GetObjectItemCaseSensitive(body_json, "code");
    if ( rev_code->valueint == 0 )
    {
        const cJSON *rev_data = cJSON_GetObjectItemCaseSensitive(body_json, "data");
        const cJSON *rev_fileList = cJSON_GetObjectItemCaseSensitive(rev_data, "fileList");
        const cJSON *rev_fileList_0 = cJSON_GetArrayItem(rev_fileList, 0);
        const cJSON *rev_label = cJSON_GetObjectItemCaseSensitive(rev_fileList_0, "label");
        label = rev_label->valueint;
        printf("label获取成功:label = %d\n", label);
    }
    else
    {
        printf("错误。错误码：%s\n",rev_code->valuestring);
    }
    if ( label < 0 )
    {
        printf("该图片文件有错误，或者格式不支持（gif图不支持）\n");
    }
    else
    {
        int objectCount = 0;
        ObjectInfo *objects = parseCsvFile("../csv_data/label返回值.csv", &objectCount);
        ObjectInfo *obj = findObjectByLabel(objects, objectCount, label);
        if (obj) 
        {
            printf("找到对象: Label=%d\n", obj->label);
            printf("英文名称: %s\n", obj->english);
            printf("中文名称: %s\n", obj->chinese);
            printf("分类: %s\n", obj->category);
        } 
        else 
        {
            printf("未找到Label=%d的对象\n", label);
        }

        // 释放内存
        freeObjects(objects);
    }
    // 释放资源
    cJSON_Delete(body_json);

    printf("\n响应接收完成, 总长度: %zu 字节\n", total_response_size);

    // 关闭连接
    close(sockfd);
    return 0;        
}